package no.rmz.genetics;


import java.util.Collection;
import java.util.HashSet;
import java.util.Random;

/**
 * Interface for breeding new instances.
 *
 * @param <T>
 */
final public class Breeder<T> {

    
    /**
     * Check if an instance of something other than Class, is an
     * instance of a genetically annotated class.
     * @param x The object.
     * @return True iff genetic.
     */
    public static boolean isGeneticInstance(final Object x){
        return  isGeneticClass(x.getClass());
    }

    /**
     * Check if a class is annotated with genetics.
     * @param x The class
     * @return True iff the class is genetically annotated.
     */
    public static boolean isGeneticClass(final Class x){
        return  (x.isAnnotationPresent(Genetic.class));
    }
    

    /**
     * Some randomness to select between objects
     */
    private Random randomness = new Random();

    
    /**
     * Select one or the other with 50% probability
     * @param g1 One alternative.
     * @param g2 Another alternative.
     * @return One of the alternatives.
     */
    private Object selectRandomly(final Object  g1, final Object g2){
        if (randomness.nextDouble() < 0.5) {
            return g1;
        } else {
            return g2;
        }
    }

    /**
     * A collection of objects use as a stack to
     * identify recursive invocations of the combinator.
     * (these are illegal and an exception will be thrown).
     */
    Collection<Object> combinationStack = new HashSet<Object>();

    /**
     * Combine two instances using genetic recombination.
     * If both of the two instances are not genetic
     * (their classes marked with "@genetic" annotations),
     * one of them is selected randomly.  Of both instances
     * are genetic, then it is assumed that they are also
     * instances of the same class (anything else throws
     * an exception).   Their genomes (the parts annotated
     * with "@gene" annotations are extracted. A new instance
     * of the same class as the parameters is produced. The genomes
     * from the parameters now presents a set of pairs of values.
     * Each of these pairs are combined genetially (i.e. recursively) 
     * and the result of the combination is put into the result object.  
     * If cyclic deendencies are discovered during the
     * recursive recombination, an exception is thrown.
     * @param g1 First piece of genetic material.
     * @param g2 Second piece of genetic material.
     * @return
     */
     private Object combineGenetically(final Object g1, final Object g2)
      throws GeneticException {
        /*
         * If genetic recombination is not possible,
         * then select one of the input parameters
         * randomlyl.
         */
        if (       (g1 == null)
                || (g2 == null)
                || !isGeneticInstance(g1)
                || !isGeneticInstance(g2)
                || (g1.getClass() != g2.getClass())){
            return selectRandomly(g1, g2); // XXX Does�t make new instance!
        }

        /*
         * Make certain that both parameters are genetic.
         */
        assert(isGeneticInstance(g1));
        assert(isGeneticInstance(g2));

        /*
         * Avoid recursive invocation of this method.
         */
        if (combinationStack.contains(g1) ||
                combinationStack.contains(g2)) {
            // XXX Use another exception!!
            throw new RuntimeException("FNORD");
        } else {
            combinationStack.add(g1);
            combinationStack.add(g2);
        }


        /*
         * Get the class of both parameters (it's the same thing).
         */
        final Class c = g1.getClass();

        /**
         * The class must be genetic, or else we're F...ed
         */
        assert (isGeneticClass(c));

        /*
         * Then fail if both parameters are not instances of
         * the same class. XXX This is quite restrictive,
         * perhaps I should be a bit less draconian,
         * for instance just select one of the components
         * instead?
         */
        assert(c == g2.getClass());


        /*
         *  Find a class specific combiner
         *  for this type of combination.
         */

        InstanceCombinator combinator =
            InstanceCombinator.getInstanceCombinator(c);

        /*
         * Compute the new combination.
         */
        final Object  result =
            combinator.combineGenetically(this, g1, g2);

        /**
         * Bump the "stack".
         */
        combinationStack.remove(g1);
        combinationStack.remove(g2);

        /*
         * Finally return the result.
         */
        return result;

    }

    /**
     * Breed something in particular, returing a typesafe
     * intance to be used in the next generation. 
     * Instances may be recycled if they are not recombined.
     * @param g1 first individual.
     * @param g2 second individual.
     * @return genetic combination of the inividual (or one of the individual if they are not both combinable)
     */
    public final T breed(T g1, T g2) throws GeneticException {
        return   (T)combineGenetically(g1, g2);
    }
}
